#include "os/os.h"

#include "utility/utility.h"

// Ubuntu Dapper requires memory pages to be marked as
// executable. Otherwise, OS raises an exception when executing code
// in that page.
#include <sys/types.h>  // mmap & munmap
#include <sys/mman.h>   // mmap & munmap
#include <sys/stat.h>   // open
#include <fcntl.h>      // open
#include <unistd.h>     // sysconf

namespace l8
{

void OS::Setup() {
    // Seed the random number generator.
    // Convert the current time to a 64-bit integer first, before converting it
    // to an unsigned. Going directly can cause an overflow and the seed to be
    // set to all ones. The seed will be identical for different instances that
    // call this setup code within the same millisecond.
    //uint64_t seed = static_cast<uint64_t>(TimeCurrentMillis());
    //srandom(static_cast<unsigned int>(seed));
}


// We keep the lowest and highest addresses mapped as a quick way of
// determining that pointers are outside the heap (used mostly in assertions
// and verification).  The estimate is conservative, ie, not all addresses in
// 'allocated' space are actually allocated to our heap.  The range is
// [lowest, highest), inclusive on the low and and exclusive on the high end.
static void* lowest_ever_allocated = reinterpret_cast<void*>(-1);
static void* highest_ever_allocated = reinterpret_cast<void*>(0);


static void UpdateAllocatedSpaceLimits(void* address, int size) {
    lowest_ever_allocated = Min(lowest_ever_allocated, address);
    highest_ever_allocated =
            Max(highest_ever_allocated,
                reinterpret_cast<void*>(reinterpret_cast<char*>(address) + size));
}


bool OS::IsOutsideAllocatedSpace(void* address) {
    return address < lowest_ever_allocated || address >= highest_ever_allocated;
}


size_t OS::AllocateAlignment() {
    return sysconf(_SC_PAGESIZE);
}

void* OS::Allocate(const size_t requested,
                   size_t* allocated,
                   bool is_executable)
{
    const size_t msize = RoundUp(requested, sysconf(_SC_PAGESIZE));
    int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
    void* mbase = mmap(NULL, msize, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (mbase == MAP_FAILED) {
        LOG(StringEvent("OS::Allocate", "mmap failed"));
        return NULL;
    }
    *allocated = msize;
    UpdateAllocatedSpaceLimits(mbase, msize);
    return mbase;
}


void OS::Free(void* address, const size_t size)
{
    // TODO(1240712): munmap has a return value which is ignored here.
    munmap(address, size);
}

} // namespace l8

